一覧に戻る

MapLibre GL JSのaddProtocolとは何なのか

#TypeScript#foss4g#MapLibre

はじめに

MapLibreとは

MapLibre Official WebSite https://maplibre.org

  • Web地図に関するプロダクトをOSSとして開発・メンテナンスしているOrganizationです
  • ブラウザ・モバイル向けの地図ライブラリおよび地図配信に関するツール群があります

MapLibre User Group Japan (MUG-JP) とは

MUG-JP Official Web Site https://mug-jp.org/

  • MapLibreプロダクトの開発者によるユーザーコミュニティです
  • ナレッジの共有やイベントの開催による普及活動を行なっています

v2で突如生えてきたaddProtocol()関数

addProtocol()の経緯などについては以下の記事が詳しいです。そういえば昔はmapbox://というプロトコルが使えたっけ…。

https://qiita.com/mg_kudo/items/cc88be1ed46fa77871fd

MapLibre Styleでは、sourceでタイル等のURLを定義しますが、その際のプレフィックスをprotocolと呼びます(http/https...)。https://ならもちろんデフォルトの挙動(単にデータをダウンロードする)となりますが、ここに独自のprotocolを定義するのがaddProtocolという訳です。

addProtocol()という言葉だけだと意味がわかりづらいので、ユーザーがタイルデータを自由に生成出来るcustom sourceと理解するのが良いと思います。

addProtocolで定義するカスタムプロトコル

割となんでも出来て、ルールはたったひとつ=「タイルデータをreturnする」ことだけです。

以下の例では、クライアントサイドでオンメモリで生成したPNG画像をreturnしています(fetchせずcanvasで画像生成しているのがポイント)。

https://svelte-maplibre-gl.mierune.dev/examples/custom-protocol より引用

maplibregl.addProtocol('myprotocol', async (params) => {
    const zxy = params.url.replace('myprotocol://', '');
    const [z, x, y] = zxy.split('/').map((v) => parseInt(v, 10));

    const png = await new Promise((resolve) => {
      const canvas = document.createElement('canvas');
      canvas.width = 512;
      canvas.height = 512;
      const context = canvas.getContext('2d')!;

      // checkered pattern
      context.fillStyle = (z + x - y) % 2 === 0 ? 'rgba(255, 255, 255, 0.4)' : 'rgba(0, 0, 0, 0.4)';
      context.fillRect(0, 0, canvas.width, canvas.height);
      context.fillStyle = 'white';
      context.font = '32px sans-serif';
      context.fillText(`${z}/${x}/${y}`, 32, 64);

      // canvas to blob (png) to arraybuffer
      canvas.toBlob(async (blob) => {
        resolve(await blob!.arrayBuffer());
      });
    });

    return { data: png };
});

また、以下の例ではベクトルタイルのバイナリをオンメモリで生成しています。

https://qiita.com/frogcat/items/b38b5e37535b65c5464b

addProtocolを活用しているライブラリ群

https://github.com/mug-jp/maplibre-gl-gsi-terrain

https://github.com/onthegomap/maplibre-contour

https://github.com/geomatico/maplibre-cog-protocol

https://github.com/jimmyrocks/maplibre-gl-vector-text-protocol

https://github.com/protomaps/PMTiles

余談:addProtocol()が実装されたあたりにあった会話

筆者所属のMIERUNEがMapLibre Orgにスポンサーしたときに、MIERUNEがMapLibreをどう利用しているか的なインタビューがあり:

https://maplibre.org/news/2022-06-10-mierune-announcement/#have-you-ever-used-maplibre-in-a-way-you-never-thought-you-would

メンテナーたちも、現状のような使われ方は予想していなかったのかもしれませんね。addProtocol()がユーザー定義のsourceの作成を可能としたことで、図らずもいわゆる「プラグイン機構」を提供するものとなったということでしょう。当初、関数のシグネチャもおよそサードパーティに利用させるような作りではなかったのも思い出されます(値を返すのがcallback(null, png, null, null)という関数だった。v4で綺麗になった)。

余談:MapLibre Nativeではどうなっているのか

MapLibre Nativeでは、そもそもデータへのリクエストは全て自分で定義する必要があります。以下は筆者が開発しているchiitilerのコードです。

https://github.com/Kanahiro/chiitiler/blob/877d53ba7c60566c65aeb12f7cf97c4b63b74681/src/render/pool.ts#L58

データフェッチングは全てこの関数を通るので、プロトコルをチェックして条件分岐させています。

ここで、GL JSとNativeは違う仕組みで動いていることに注意が必要です。つまりaddProtocolに過度に依存したstyleはNativeでは利用出来ないということです。